package com.jing.ads.downloader;

import java.io.File;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;

import android.app.AlarmManager;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.database.CharArrayBuffer;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.os.Process;
import android.util.Config;
import android.util.Log;

import com.jing.ads.util.NetworkUtil;
import com.jing.ads.util.NumberUtil;

/**
 * Performs the background downloads requested by applications that use the Downloads provider.
 * 
 */
public class DownloadService extends Service {
	
	/* ------------ Constants ------------ */

    /* ------------ Members ------------ */

	/** Observer to get notified when the content observer's data changes */
	private DownloadManagerContentObserver mObserver;
	
	/** Class to handle Notification Manager updates */
	private DownloadNotification mNotifier;
	
	/**
     * The Service's view of the list of downloads. This is kept independently
     * from the content provider, and the Service only initiates downloads
     * based on this data, so that it can deal with situation where the data
     * in the content provider changes or disappears.
     */
	private ArrayList<DownloadInfo> mDownloads;
	
	/**
     * The thread that updates the internal download list from the content
     * provider.
     */
	private UpdateThread mUpdateThread;
	
	/**
     * Whether the internal download list should be updated from the content
     * provider.
     */
	private boolean mPendingUpdate;
	
	/**
     * Array used when extracting strings from content provider
     */
    private CharArrayBuffer oldChars;

    /**
     * Array used when extracting strings from content provider
     */
    private CharArrayBuffer newChars;

	private Object info;
	
    /* ------------ Inner Classes ------------ */
    
    /**
     * Receives notifications when the data in the content provider changes
     */
    private class DownloadManagerContentObserver extends ContentObserver {

        public DownloadManagerContentObserver() {
            super(new Handler());
        }

        /**
         * Receives notification when the data in the observed content
         * provider changes.
         */
        public void onChange(final boolean selfChange) {
            if (Constants.LOGVV) {
                Log.v(Constants.TAG, "Service ContentObserver received notification");
            }
            updateFromProvider();
        }

    }
	
    /* ------------ Methods ------------ */
    
	/**
     * Returns an IBinder instance when someone wants to connect to this
     * service. Binding to this service is not allowed.
     *
     * @throws UnsupportedOperationException
     */
    public IBinder onBind(Intent i) {
        throw new UnsupportedOperationException("Cannot bind to Market Download Service");
    }
	
    /**
     * Initializes the service when it is first created
     */
	@Override
	public void onCreate() {
		super.onCreate();
		if (Constants.LOGVV) {
            Log.v(Constants.TAG, "Service onCreate");
        }
		
		mDownloads = new ArrayList<DownloadInfo>();
		
		mObserver = new DownloadManagerContentObserver();
        getContentResolver().registerContentObserver(Downloads.CONTENT_URI, true, mObserver);
		
        mNotifier = new DownloadNotification(this);
        mNotifier.updateNotification();
        
        trimDatabase();
        removeSpuriousFiles();
        updateFromProvider();
	}
	
	/**
     * Responds to a call to startService
     */
	@Override
	public void onStart(Intent intent, int startId) {
		super.onStart(intent, startId);
		if (Constants.LOGVV) {
            Log.v(Constants.TAG, "Service onStart");
        }
		
		updateFromProvider();
	}
	
	/**
     * Cleans up when the service is destroyed
     */
	@Override
	public void onDestroy() {
		getContentResolver().unregisterContentObserver(mObserver);
		if (Constants.LOGVV) {
            Log.v(Constants.TAG, "Service onDestroy");
        }
		super.onDestroy();
	}
	
	/**
     * Parses data from the content provider into private array
     */
	private void updateFromProvider() {
		synchronized (this) {
			mPendingUpdate = true;
			if (mUpdateThread == null) {
				mUpdateThread = new UpdateThread();
				mUpdateThread.start();
			}
		}
    }
	

	class UpdateThread extends Thread {
		
		public UpdateThread() {
            super("Download Service");
        }
		
		@Override
		public void run() {
			Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
			
			boolean keepService = false;
            // for each update from the database, remember which download is
            // supposed to get restarted soonest in the future
            long wakeUp = Long.MAX_VALUE;
            for (;;) {
            	synchronized (DownloadService.this) {
            		if (mUpdateThread != this) {
    	                throw new IllegalStateException("multiple UpdateThreads in DownloadService");
    	            }
            		if (!mPendingUpdate) {
            			mUpdateThread = null;
                        if (!keepService) {
                            stopSelf();
                        }
                        if (wakeUp != Long.MAX_VALUE) {
                            AlarmManager alarms = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
                            if (alarms == null) {
                                Log.e(Constants.TAG, "couldn't get alarm manager");
                            } else {
                                if (Constants.LOGV) {
                                    Log.v(Constants.TAG, "scheduling retry in " + wakeUp + "ms");
                                }
                                Intent intent = new Intent(Constants.ACTION_RETRY);
                                intent.setClassName(DownloadReceiver.class.getPackage().getName(),
                                        DownloadReceiver.class.getName());
                                alarms.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + wakeUp,
                                        PendingIntent.getBroadcast(DownloadService.this, 0, intent,
                                                PendingIntent.FLAG_ONE_SHOT));
                            }
                        }
                        oldChars = null;
                        newChars = null;
                        return;
                    }
            		mPendingUpdate = false;
            	}
            	 boolean networkAvailable = NetworkUtil.isNetworkAvailable(DownloadService.this);
                 boolean networkRoaming = NetworkUtil.isNetworkRoaming(DownloadService.this);
                 long now = System.currentTimeMillis();
                 
                 Cursor cursor = getContentResolver().query(Downloads.CONTENT_URI,
                         null, null, null, Downloads._ID);
                 
                 if (cursor == null) {
                     return;
                 }

                 cursor.moveToFirst();

                 int arrayPos = 0;

                 //boolean mustScan = false;
                 keepService = false;
                 wakeUp = Long.MAX_VALUE;

                 boolean isAfterLast = cursor.isAfterLast();

                 int idColumn = cursor.getColumnIndexOrThrow(Downloads._ID);
                 /*
                  * Walk the cursor and the local array to keep them in sync. The key
                  *     to the algorithm is that the ids are unique and sorted both in
                  *     the cursor and in the array, so that they can be processed in
                  *     order in both sources at the same time: at each step, both
                  *     sources point to the lowest id that hasn't been processed from
                  *     that source, and the algorithm processes the lowest id from
                  *     those two possibilities.
                  * At each step:
                  * -If the array contains an entry that's not in the cursor, remove the
                  *     entry, move to next entry in the array.
                  * -If the array contains an entry that's in the cursor, nothing to do,
                  *     move to next cursor row and next array entry.
                  * -If the cursor contains an entry that's not in the array, insert
                  *     a new entry in the array, move to next cursor row and next
                  *     array entry.
                  */
                 while (!isAfterLast || arrayPos < mDownloads.size()) {
                     if (isAfterLast) {
                         // We're beyond the end of the cursor but there's still some
                         //     stuff in the local array, which can only be junk
                         if (Constants.LOGVV) {
                             int arrayId = ((DownloadInfo) mDownloads.get(arrayPos)).id;
                             Log.v(Constants.TAG, "Array update: trimming " +
                                     arrayId + " @ "  + arrayPos);
                         }
                         deleteDownload(arrayPos); // this advances in the array
                     } else {
                         int id = cursor.getInt(idColumn);

                         if (arrayPos == mDownloads.size()) {
                             insertDownload(cursor, arrayPos, networkAvailable, networkRoaming, now);
                             if (Constants.LOGVV) {
                                 Log.v(Constants.TAG, "Array update: inserting " +
                                         id + " @ " + arrayPos);
                             }
                             if (visibleNotification(arrayPos)) {
                                 keepService = true;
                             }
                             long next = nextAction(arrayPos, now);
                             if (next == 0) {
                                 keepService = true;
                             } else if (next > 0 && next < wakeUp) {
                                 wakeUp = next;
                             }
                             ++arrayPos;
                             cursor.moveToNext();
                             isAfterLast = cursor.isAfterLast();
                         } else {
                             int arrayId = mDownloads.get(arrayPos).id;

                             if (arrayId < id) {
                                 // The array entry isn't in the cursor
                                 if (Constants.LOGVV) {
                                     Log.v(Constants.TAG, "Array update: removing " + arrayId
                                             + " @ " + arrayPos);
                                 }
//                                 if (shouldScanFile(arrayPos) && mediaScannerConnected()) {
//                                     scanFile(null, arrayPos);
//                                 }
                                 deleteDownload(arrayPos); // this advances in the array
                             } else if (arrayId == id) {
                                 // This cursor row already exists in the stored array
                                 updateDownload(
                                         cursor, arrayPos,
                                         networkAvailable, networkRoaming, now);
//                                 if (shouldScanFile(arrayPos)
//                                         && (!mediaScannerConnected()
//                                                 || !scanFile(cursor, arrayPos))) {
//                                     mustScan = true;
//                                     keepService = true;
//                                 }
                                 if (visibleNotification(arrayPos)) {
                                     keepService = true;
                                 }
                                 long next = nextAction(arrayPos, now);
                                 if (next == 0) {
                                     keepService = true;
                                 } else if (next > 0 && next < wakeUp) {
                                     wakeUp = next;
                                 }
                                 ++arrayPos;
                                 cursor.moveToNext();
                                 isAfterLast = cursor.isAfterLast();
                             } else {
                                 // This cursor entry didn't exist in the stored array
                                 if (Constants.LOGVV) {
                                     Log.v(Constants.TAG, "Array update: appending " +
                                             id + " @ " + arrayPos);
                                 }
                                 insertDownload(cursor, arrayPos,
                                         networkAvailable, networkRoaming, now);
//                                 if (shouldScanFile(arrayPos)
//                                         && (!mediaScannerConnected()
//                                                 || !scanFile(cursor, arrayPos))) {
//                                     mustScan = true;
//                                     keepService = true;
//                                 }
                                 if (visibleNotification(arrayPos)) {
                                     keepService = true;
                                 }
                                 long next = nextAction(arrayPos, now);
                                 if (next == 0) {
                                     keepService = true;
                                 } else if (next > 0 && next < wakeUp) {
                                     wakeUp = next;
                                 }
                                 ++arrayPos;
                                 cursor.moveToNext();
                                 isAfterLast = cursor.isAfterLast();
                             }
                         }
                     }
                 }
                 
                 mNotifier.updateNotification();
                 cursor.close();
            }
		}
	}
		
	/**
     * Removes files that may have been left behind in the cache directory
     */
    private void removeSpuriousFiles() {
        File[] files = Environment.getDownloadCacheDirectory().listFiles();
        if (files == null) {
            // The cache folder doesn't appear to exist (this is likely the case
            // when running the simulator).
            return;
        }
        HashSet<String> fileSet = new HashSet<String>();
        for (int i = 0; i < files.length; i++) {
            if (files[i].getName().equals(Constants.KNOWN_SPURIOUS_FILENAME)) {
                continue;
            }
            if (files[i].getName().equalsIgnoreCase(Constants.RECOVERY_DIRECTORY)) {
                continue;
            }
            fileSet.add(files[i].getPath());
        }

        Cursor cursor = getContentResolver().query(Downloads.CONTENT_URI,
                new String[] { Downloads._DATA }, null, null, null);
        if (cursor != null) {
            if (cursor.moveToFirst()) {
                do {
                    fileSet.remove(cursor.getString(0));
                } while (cursor.moveToNext());
            }
            cursor.close();
        }
        Iterator<String> iterator = fileSet.iterator();
        while (iterator.hasNext()) {
            String filename = iterator.next();
            if (Constants.LOGV) {
                Log.v(Constants.TAG, "deleting spurious file " + filename);
            }
            new File(filename).delete();
        }
    }
    
    /**
     * Drops old rows from the database to prevent it from growing too large
     */
    private void trimDatabase() {
        Cursor cursor = getContentResolver().query(Downloads.CONTENT_URI,
                new String[] { Downloads._ID },
                Downloads.COLUMN_STATUS + " >= '200'", null,
                Downloads.COLUMN_LAST_MODIFICATION);
        if (cursor == null) {
            // This isn't good - if we can't do basic queries in our database, nothing's gonna work
            Log.e(Constants.TAG, "null cursor in trimDatabase");
            return;
        }
        if (cursor.moveToFirst()) {
            int numDelete = cursor.getCount() - Constants.MAX_DOWNLOADS;
            int columnId = cursor.getColumnIndexOrThrow(Downloads._ID);
            while (numDelete > 0) {
                getContentResolver().delete(
                        ContentUris.withAppendedId(Downloads.CONTENT_URI, cursor.getLong(columnId)),
                        null, null);
                if (!cursor.moveToNext()) {
                    break;
                }
                numDelete--;
            }
        }
        cursor.close();
    }
    
    /**
     * Keeps a local copy of the info about a download, and initiates the
     * download if appropriate.
     */
    private void insertDownload(
            Cursor cursor, int arrayPos,
            boolean networkAvailable, boolean networkRoaming, long now) {
        int statusColumn = cursor.getColumnIndexOrThrow(Downloads.COLUMN_STATUS);
        int failedColumn = cursor.getColumnIndexOrThrow(Constants.FAILED_CONNECTIONS);
        int retryRedirect = cursor.getInt(cursor.getColumnIndexOrThrow(Constants.RETRY_AFTER___REDIRECT_COUNT));
		DownloadInfo info = new DownloadInfo(
        cursor.getInt(cursor.getColumnIndexOrThrow(Downloads._ID)),
        cursor.getString(cursor.getColumnIndexOrThrow(Downloads.COLUMN_URI)),
        cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_NO_INTEGRITY)) == 1,
        cursor.getString(cursor.getColumnIndexOrThrow(Downloads.COLUMN_FILE_NAME_HINT)),
        cursor.getString(cursor.getColumnIndexOrThrow(Downloads._DATA)),
        cursor.getString(cursor.getColumnIndexOrThrow(Downloads.COLUMN_MIME_TYPE)),
        cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_DESTINATION)),
        cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_VISIBILITY)),
        cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_CONTROL)),
        cursor.getInt(statusColumn),
        cursor.getInt(failedColumn),
        retryRedirect & 0xfffffff,
        retryRedirect >> 28,
        cursor.getLong(cursor.getColumnIndexOrThrow(Downloads.COLUMN_LAST_MODIFICATION)),
        /*cursor.getString(cursor.getColumnIndexOrThrow(Downloads.COLUMN_NOTIFICATION_PACKAGE)),
        cursor.getString(cursor.getColumnIndexOrThrow(Downloads.COLUMN_NOTIFICATION_CLASS)),*/
        cursor.getString(cursor.getColumnIndexOrThrow(Downloads.COLUMN_NOTIFICATION_EXTRAS)),
        cursor.getString(cursor.getColumnIndexOrThrow(Downloads.COLUMN_COOKIE_DATA)),
        cursor.getString(cursor.getColumnIndexOrThrow(Downloads.COLUMN_USER_AGENT)),
        cursor.getString(cursor.getColumnIndexOrThrow(Downloads.COLUMN_REFERER)),
        cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_TOTAL_BYTES)),
        cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_CURRENT_BYTES)),
        cursor.getString(cursor.getColumnIndexOrThrow(Constants.ETAG)),
        cursor.getString(cursor.getColumnIndexOrThrow(Downloads.COLUMN_TITLE))//,
        //cursor.getInt(cursor.getColumnIndexOrThrow(Constants.MEDIA_SCANNED)) == 1
        );

        if (Constants.LOGVV) {
            Log.v(Constants.TAG, "Service adding new entry");
            Log.v(Constants.TAG, "ID      : " + info.id);
            Log.v(Constants.TAG, "URI     : " + ((info.uri != null) ? "yes" : "no"));
            Log.v(Constants.TAG, "NO_INTEG: " + info.noIntegrity);
            Log.v(Constants.TAG, "HINT    : " + info.hint);
            Log.v(Constants.TAG, "FILENAME: " + info.filename);
            Log.v(Constants.TAG, "MIMETYPE: " + info.mimetype);
            Log.v(Constants.TAG, "DESTINAT: " + info.destination);
            Log.v(Constants.TAG, "VISIBILI: " + info.visibility);
            Log.v(Constants.TAG, "CONTROL : " + info.control);
            Log.v(Constants.TAG, "STATUS  : " + info.status);
            Log.v(Constants.TAG, "FAILED_C: " + info.numFailed);
            Log.v(Constants.TAG, "RETRY_AF: " + info.retryAfter);
            Log.v(Constants.TAG, "REDIRECT: " + info.redirectCount);
            Log.v(Constants.TAG, "LAST_MOD: " + info.lastMod);
//            Log.v(Constants.TAG, "PACKAGE : " + info.pckg);
//            Log.v(Constants.TAG, "CLASS   : " + info.clazz);
            Log.v(Constants.TAG, "COOKIES : " + ((info.cookies != null) ? "yes" : "no"));
            Log.v(Constants.TAG, "AGENT   : " + info.userAgent);
            Log.v(Constants.TAG, "REFERER : " + ((info.referer != null) ? "yes" : "no"));
            Log.v(Constants.TAG, "TOTAL   : " + info.totalBytes);
            Log.v(Constants.TAG, "CURRENT : " + info.currentBytes);
            Log.v(Constants.TAG, "ETAG    : " + info.etag);
        }

        mDownloads.add(arrayPos, info);

        if (info.status == 0
                && (info.destination == Downloads.DESTINATION_EXTERNAL
                    || info.destination == Downloads.DESTINATION_CACHE_PARTITION_PURGEABLE)
                && info.mimetype != null
                /*&& !DrmRawContent.DRM_MIMETYPE_MESSAGE_STRING.equalsIgnoreCase(info.mimetype)*/) {
            // Check to see if we are allowed to download this file. Only files
            // that can be handled by the platform can be downloaded.
            // special case DRM files, which we should always allow downloading.
            Intent mimetypeIntent = new Intent(Intent.ACTION_VIEW);
            
            // We can provide data as either content: or file: URIs,
            // so allow both.  (I think it would be nice if we just did
            // everything as content: URIs)
            // Actually, right now the download manager's UId restrictions
            // prevent use from using content: so it's got to be file: or
            // nothing
            
            mimetypeIntent.setDataAndType(Uri.fromParts("file", "", null), info.mimetype);
            ResolveInfo ri = getPackageManager().resolveActivity(mimetypeIntent,
                    PackageManager.MATCH_DEFAULT_ONLY);
            //Log.i(Constants.TAG, "*** QUERY " + mimetypeIntent + ": " + list);
            
            if (ri == null) {
                if (Config.LOGD) {
                    Log.d(Constants.TAG, "no application to handle MIME type " + info.mimetype);
                }
                info.status = Downloads.STATUS_NOT_ACCEPTABLE;

                Uri uri = ContentUris.withAppendedId(Downloads.CONTENT_URI, info.id);
                ContentValues values = new ContentValues();
                values.put(Downloads.COLUMN_STATUS, Downloads.STATUS_NOT_ACCEPTABLE);
                getContentResolver().update(uri, values, null, null);
                info.sendIntentIfRequested(uri, this);
                return;
            }
        }

        if (info.canUseNetwork(networkAvailable, networkRoaming)) {
            if (info.isReadyToStart(now)) {
                if (Constants.LOGV) {
                    Log.v(Constants.TAG, "Service spawning thread to handle new download " +
                            info.id);
                }
                if (info.hasActiveThread) {
                    throw new IllegalStateException("Multiple threads on same download on insert");
                }
                if (info.status != Downloads.STATUS_RUNNING) {
                    info.status = Downloads.STATUS_RUNNING;
                    ContentValues values = new ContentValues();
                    values.put(Downloads.COLUMN_STATUS, info.status);
                    getContentResolver().update(
                            ContentUris.withAppendedId(Downloads.CONTENT_URI, info.id),
                            values, null, null);
                }
                DownloadThread downloader = new DownloadThread(this, info);
                info.hasActiveThread = true;
                downloader.start();
            }
        } else {
            if (info.status == 0
                    || info.status == Downloads.STATUS_PENDING
                    || info.status == Downloads.STATUS_RUNNING) {
                info.status = Downloads.STATUS_RUNNING_PAUSED;
                Uri uri = ContentUris.withAppendedId(Downloads.CONTENT_URI, info.id);
                ContentValues values = new ContentValues();
                values.put(Downloads.COLUMN_STATUS, Downloads.STATUS_RUNNING_PAUSED);
                getContentResolver().update(uri, values, null, null);
            }
        }
    }
    
    private void sentDownloadMsg (String pkname) {
    	Intent i = new Intent("com.jing.ads.notify.cancel");
    	i.putExtra("pName", pkname);
    	sendBroadcast(i);
    }
    
    /**
     * Updates the local copy of the info about a download.
     */
    private void updateDownload(
            Cursor cursor, int arrayPos,
            boolean networkAvailable, boolean networkRoaming, long now) {
        DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos);
        int statusColumn = cursor.getColumnIndexOrThrow(Downloads.COLUMN_STATUS);
        int failedColumn = cursor.getColumnIndexOrThrow(Constants.FAILED_CONNECTIONS);
        info.id = cursor.getInt(cursor.getColumnIndexOrThrow(Downloads._ID));
        info.uri = stringFromCursor(info.uri, cursor, Downloads.COLUMN_URI);
        info.noIntegrity =
                cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_NO_INTEGRITY)) == 1;
        info.hint = stringFromCursor(info.hint, cursor, Downloads.COLUMN_FILE_NAME_HINT);
        info.filename = stringFromCursor(info.filename, cursor, Downloads._DATA);
        info.mimetype = stringFromCursor(info.mimetype, cursor, Downloads.COLUMN_MIME_TYPE);
        info.destination = cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_DESTINATION));
        int newVisibility = cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_VISIBILITY));
        if (info.visibility == Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED
                && newVisibility != Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED
                && Downloads.isStatusCompleted(info.status)) {
//            mNotifier.mNotificationMgr.cancel(info.id);
        	//TODO 
//        	sentDownloadMsg(info.extras);
        }
        info.visibility = newVisibility;
        synchronized(info) {
            info.control = cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_CONTROL));
        }
        int newStatus = cursor.getInt(statusColumn);
        if (!Downloads.isStatusCompleted(info.status) && Downloads.isStatusCompleted(newStatus)) {
//            mNotifier.mNotificationMgr.cancel(info.id);
        	//TODO
        }
        info.status = newStatus;
        info.numFailed = cursor.getInt(failedColumn);
        int retryRedirect =
                cursor.getInt(cursor.getColumnIndexOrThrow(Constants.RETRY_AFTER___REDIRECT_COUNT));
        info.retryAfter = retryRedirect & 0xfffffff;
        info.redirectCount = retryRedirect >> 28;
        info.lastMod = cursor.getLong(cursor.getColumnIndexOrThrow(Downloads.COLUMN_LAST_MODIFICATION));
//        info.pckg = stringFromCursor(info.pckg, cursor, Downloads.COLUMN_NOTIFICATION_PACKAGE);
//        info.clazz = stringFromCursor(info.clazz, cursor, Downloads.COLUMN_NOTIFICATION_CLASS);
        info.cookies = stringFromCursor(info.cookies, cursor, Downloads.COLUMN_COOKIE_DATA);
        info.userAgent = stringFromCursor(info.userAgent, cursor, Downloads.COLUMN_USER_AGENT);
        info.referer = stringFromCursor(info.referer, cursor, Downloads.COLUMN_REFERER);
        info.totalBytes = cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_TOTAL_BYTES));
        info.currentBytes = cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.COLUMN_CURRENT_BYTES));
        info.etag = stringFromCursor(info.etag, cursor, Constants.ETAG);
        //info.mediaScanned =
        //        cursor.getInt(cursor.getColumnIndexOrThrow(Constants.MEDIA_SCANNED)) == 1;

        if (info.canUseNetwork(networkAvailable, networkRoaming)) {
            if (info.isReadyToRestart(now)) {
                if (Constants.LOGV) {
                    Log.v(Constants.TAG, "Service spawning thread to handle updated download " +
                            info.id);
                }
                if (info.hasActiveThread) {
                    throw new IllegalStateException("Multiple threads on same download on update");
                }
                info.status = Downloads.STATUS_RUNNING;
                ContentValues values = new ContentValues();
                values.put(Downloads.COLUMN_STATUS, info.status);
                getContentResolver().update(
                        ContentUris.withAppendedId(Downloads.CONTENT_URI, info.id),
                        values, null, null);
                DownloadThread downloader = new DownloadThread(this, info);
                info.hasActiveThread = true;
                downloader.start();
            }
        }
    }
    
    /**
     * Returns a String that holds the current value of the column,
     * optimizing for the case where the value hasn't changed.
     */
    private String stringFromCursor(String old, Cursor cursor, String column) {
        int index = cursor.getColumnIndexOrThrow(column);
        if (old == null) {
            return cursor.getString(index);
        }
        if (newChars == null) {
            newChars = new CharArrayBuffer(128);
        }
        cursor.copyStringToBuffer(index, newChars);
        int length = newChars.sizeCopied;
        if (length != old.length()) {
            return cursor.getString(index);
        }
        if (oldChars == null || oldChars.sizeCopied < length) {
            oldChars = new CharArrayBuffer(length);
        }
        char[] oldArray = oldChars.data;
        char[] newArray = newChars.data;
        old.getChars(0, length, oldArray, 0);
        for (int i = length - 1; i >= 0; --i) {
            if (oldArray[i] != newArray[i]) {
                return new String(newArray, 0, length);
            }
        }
        return old;
    }
    
    /**
     * Removes the local copy of the info about a download.
     */
    private void deleteDownload(int arrayPos) {
        DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos);
        if (info.status == Downloads.STATUS_RUNNING) {
            info.status = Downloads.STATUS_CANCELED;
        } else if (info.destination != Downloads.DESTINATION_EXTERNAL && info.filename != null) {
            new File(info.filename).delete();
        }
//        mNotifier.mNotificationMgr.cancel(info.id);
        //TODO

        mDownloads.remove(arrayPos);
    }
    
    /**
     * Returns the amount of time (as measured from the "now" parameter)
     * at which a download will be active.
     * 0 = immediately - service should stick around to handle this download.
     * -1 = never - service can go away without ever waking up.
     * positive value - service must wake up in the future, as specified in ms from "now"
     */
    private long nextAction(int arrayPos, long now) {
        DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos);
        if (Downloads.isStatusCompleted(info.status)) {
            return -1;
        }
        if (info.status != Downloads.STATUS_RUNNING_PAUSED) {
            return 0;
        }
        if (info.numFailed == 0) {
            return 0;
        }
        long when = info.restartTime();
        if (when <= now) {
            return 0;
        }
        return when - now;
    }
    
    /**
     * Returns whether there's a visible notification for this download
     */
    private boolean visibleNotification(int arrayPos) {
        DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos);
        return info.hasCompletionNotification();
    }
    
    public static void cancel(Context context, Uri uri) {
    	//contentResolver.delete(uri, null, null);
    	
//		ContentValues values = new ContentValues();
//		values.put(Downloads.STATUS, Downloads.STATUS_CANCELED);
//		contentResolver.update(uri, values, null, null);
		try {
			ContentValues values = new ContentValues();
	    	values.put(Downloads.COLUMN_VISIBILITY, Downloads.VISIBILITY_HIDDEN);
			values.put(Downloads.COLUMN_CONTROL, Downloads.CONTROL_PAUSED);
			context.getContentResolver().update(uri, values, null, null);
			
			NotificationManager mNotificationMgr = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
			String id = uri.getPathSegments().get(1);
			mNotificationMgr.cancel(NumberUtil.toInt(id));
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}